﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Graphix.Elements;
using Graphix.Helpers;

namespace Graphix.Charts
{
    using System.Globalization;

    using CommonHelpers;

    using Graphix.Charts.Base;
    using Graphix.Charts.Values;

    /// <summary>
    /// The candlestick chart.
    /// </summary>
    public class CandlestickChart : BothAxisAndLegend<Candle>
    {
        public void AddSets(List<string> sets)
        {
            foreach (string s in sets)
            {
                this.SetsName.Add(s);
            }
        }

        protected override void DrawChart()
        {
            if (this.DisplayLegend)
            {
                this.BuildLegend(this.SetsName, this.values);
            }

            this.DrawTitle();
            this.AddYAxisTitle();
            this.AddXAxisTitle();

            this.AddTicks();

            this.AddBars();

            this.AddElement(new Line(this.Border.Left,
                this.Border.Top + this.Border.Height / 2,
                this.Border.Left + this.Border.Width, this.Border.Top + this.Border.Height / 2,
                Color.Black));

            this.AddElement(new Line(this.Border.Width / 2 + this.Border.Left,
                this.Border.Top,
                this.Border.Width / 2 + this.Border.Left, this.Border.Top + this.Border.Height,
                Color.Black));
        }

        private void AddTicks()
        {
            if (float.IsNaN(this.YAxis.Minimum) || this.YAxis.Minimum != 0)
            {
                float barMin = this.values.Min(x => x.Values.Min(y => y.GetMinimum()));
                if (float.IsNaN(this.YAxis.Minimum) || this.YAxis.Minimum > barMin)
                {
                    this.YAxis.Minimum = barMin;
                }
            }

            if (float.IsNaN(this.YAxis.Maximum) ||
                this.YAxis.Maximum == 0 ||
                this.YAxis.Maximum < this.YAxis.Minimum)
            {
                float barMax = this.values.Max(x => x.Values.Max(y => y.GetMaximum()));
                if (float.IsNaN(this.YAxis.Maximum) || this.YAxis.Maximum < barMax)
                {
                    this.YAxis.Maximum = barMax;
                }
            }

            this.YAxis.CalculateScale();

            float left = this.Border.Left;
            float right = this.Border.Right;
            int ticks = this.values.Max(x => x.Values.Count);
            ticks--;

            float border = 1;
            float yticks = (int)(this.YAxis.Scale.Range / this.YAxis.Scale.TickSpacing);

            if (this.YAxis.DisplayValues)
            {
                string longest = this.YAxis.Scale.NiceMin.ToString(CultureInfo.InvariantCulture);
                if (this.YAxis.Scale.NiceMax.ToString(CultureInfo.InvariantCulture).Length > longest.Length)
                {
                    longest = this.YAxis.Scale.NiceMax.ToString(CultureInfo.InvariantCulture);
                }

                var measured = DefaultGraphics.Instance.MeasureDisplayString(longest, this.TextStyle.Font);
                this.Border.Left += measured.Width + 2;
            }

            int groups = this.SetsName.Count;
            float width = this.Border.Width / groups;
            float barwidth = this.Border.Width / ((1 + (float)Math.Sqrt(5)) / 2);
            barwidth /= groups;
            float singlebarWidth = barwidth;
            barwidth /= this.values.Count;
            // X
            left = this.Border.Left;
            left += (this.Border.Width - (width * (groups - 1)) - (barwidth * this.values.Count))
                    / 2f;
            left += singlebarWidth / 2f;
            float tickSize = this.Border.Width / groups;
            if (this.XAxis.DisplayValues)
            {
                var measured = DefaultGraphics.Instance.MeasureDisplayString(this.SetsName[0], this.TextStyle.Font);
                this.Border.Bottom -= measured.Height + 2;

                for (int i = 0; i <= ticks; i++)
                {
                    float current;
                    if (this.XAxis.OppositeDirection)
                    {
                        current = left + (tickSize * (ticks - i));
                    }
                    else
                    {
                        current = left + (tickSize * i);
                    }

                    string text = string.Format(this.XAxis.Format, this.SetsName[i]);
                    this.AddElement(new ChartString(current, this.Border.Bottom, 0, 0,
                        text, this.XAxis.TextStyle.Font)
                        {
                            Format = new StringFormat() { Alignment = StringAlignment.Center },
                            MoveWhen = PerformMove.Left | PerformMove.Right,
                            Color = this.XAxis.TextStyle.ColorBrush
                        });
                }
            }

            float bottom = this.Border.Bottom;

            // Y
            left = this.Border.Left;
            
            float ytickSize = this.Border.Height / yticks;
            for (int i = 0; i <= yticks; i++)
            {
                float current;
                if (this.YAxis.OppositeDirection)
                {
                    current = bottom - (ytickSize * (yticks - i));
                }
                else
                {
                    current = bottom - (ytickSize * i);
                }

                this.AddElement(new Line(left, current, right, current, this.YAxis.MajorColor)
                    {
                        MoveWhen = PerformMove.Bottom
                    });

                if (this.YAxis.DisplayValues)
                {
                    StringAlignment alignment;
                    if (this.YAxis.OppositeDirection)
                    {
                        if (i == 0)
                        {
                            alignment = StringAlignment.Near;
                        }
                        else if (i >= yticks)
                        {
                            alignment = StringAlignment.Far;
                        }
                        else
                        {
                            alignment = StringAlignment.Center;
                        }
                    }
                    else
                    {
                        if (i == 0)
                        {
                            alignment = StringAlignment.Far;
                        }
                        else if (i >= yticks)
                        {
                            alignment = StringAlignment.Near;
                        }
                        else
                        {
                            alignment = StringAlignment.Center;
                        }
                    }

                    string text = (this.YAxis.Scale.NiceMin + this.YAxis.Scale.TickSpacing * i).ToString(
                        CultureInfo.InvariantCulture);
                    text = string.Format(this.YAxis.Format, text);

                    this.AddElement(new ChartString(left, current, 0, 0,
                        text, this.YAxis.TextStyle.Font)
                        {
                            Format = new StringFormat()
                                {
                                    Alignment = StringAlignment.Far,
                                    LineAlignment = alignment
                                },
                            Color = this.YAxis.TextStyle.ColorBrush,
                            MoveWhen = PerformMove.Bottom
                        });
                }
            }
        }

        protected void AddBars()
        {
            float bottom = this.Border.Bottom - 1;
            float length = this.Border.Height - 1;
            float position = this.Border.Left + 1;
            int groups = this.SetsName.Count;
            float width = this.Border.Width / groups;
            float top = this.Border.Top;

            float barwidth = this.Border.Width / ((1 + (float)Math.Sqrt(5)) / 2);
            barwidth /= groups;
            barwidth /= this.values.Count;

            float space = 1;
            float division = this.YAxis.Scale.Range;
            float minus = this.YAxis.Scale.NiceMin;

            position += (this.Border.Width - (width * 4) - ((barwidth + space + 1) * this.values.Count)) / 2f;

            for (int i = 0; i < this.values.Count; i++)
            {
                ValueSet<Candle> information = this.values[i];
                Color lineColor = Color.FromArgb((int)(255 * 0.9), information.Color);
                // 8
                float bigPartPosition = position + ((barwidth + space + 1) * i);
                int loop = information.Values.Count - 1;
                for (int j = 0; j <= loop; j++)
                {
                    float smallPartPosition;
                    if (this.XAxis.OppositeDirection)
                    {
                        smallPartPosition = bigPartPosition + (width * (loop - j));
                    }
                    else
                    {
                        smallPartPosition = bigPartPosition + (width * j);
                    }

                    Candle value = information.Values[j];
                    if (value.Equals(new Candle()))
                    {
                        continue;
                    }

                    float startLine = ((value.StartLine - minus) / division) * length;
                    float startBar = ((value.StartBar - minus) / division) * length;
                    float endLine = ((value.EndLine - minus) / division) * length;
                    float endBar = ((value.EndBar - minus) / division) * length;
                    bool fill = true;

                    if (startBar > endBar)
                    {
                        var start = endBar;
                        endBar = startBar;
                        startBar = start;
                        fill = false;
                    }

                    if (startLine > endLine)
                    {
                        var start = endLine;
                        endLine = startLine;
                        startLine = start;
                    }

                    float x = smallPartPosition + (barwidth / 2);
                    float y1;
                    float y2;
                    float barPosition;
                    if (this.YAxis.OppositeDirection)
                    {
                        y1 = top + bottom - (bottom - endLine);
                        y2 = top + bottom - (bottom - endBar);
                    }
                    else
                    {
                        y1 = bottom - endLine;
                        y2 = bottom - endBar;
                    }

                    // top line
                    this.AddElement(new Line(x, y1, x, y2, new Pen(lineColor, 3)));

                    if (this.YAxis.OppositeDirection)
                    {
                        y1 = top + bottom - (bottom - startBar);
                        y2 = top + bottom - (bottom - startLine);

                        barPosition = y1;
                    }
                    else
                    {
                        y1 = bottom - startBar;
                        y2 = bottom - startLine;

                        barPosition = bottom - endBar;
                    }

                    // bottom line
                    this.AddElement(new Line(x, y1, x, y2, new Pen(lineColor, 3)));

                    // middle box
                    var element = new Rect(smallPartPosition, barPosition,
                        barwidth,
                        Math.Abs(endBar - startBar),
                        information.Color)
                        { BorderWidth = 2, FillColor = fill ? null : Brushes.Transparent };
                    element.Attributes["g"] = i.ToString(CultureInfo.InvariantCulture);
                    string legend = this.SetsName[j];
                    if (string.IsNullOrEmpty(legend))
                    {
                        legend = " ";
                    }
                    element.Attributes["data-ot"] = string.Format("l={0};v={{\"{1}\": \"{2} - {3}, {4} - {5}\"}}", legend, information.Name,
                        value.StartLine, value.EndLine, value.StartBar, value.EndBar);
                    this.AddElement(element);
                }
            }
        }

        public void AddSeries(string name, List<Candle> values)
        {
            this.values.Add(new ValueSet<Candle>(this.GetNextColor(), name, values));
        }
    }
}
